package eu.catlab.bbgs;

/**
 * This class is used to communicate with the Payment Gateway
 * 
 * Example:
 * Credits credits = new Credits ("browser-based-games", "http://www.gambic.com/");
 * credits.setEmail("daedelson@gmail.com");
 * credits.setOpenID("http://id.browser-based-games.org/user/daedeloth/");
 * credits.setPrivateKeyFile (new File ("key.pkcs8"));
 * int creds = credits.getCredits();
 * System.out.println ("You have " + creds + " credits.");
 * String url = credits.requestCredits(100, "Test", "http://www.google.com/", "test action");
 * System.out.println ("Request URL: " + url);
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.kolich.security.PKCS8RSASigner;

public class Credits
{
	private String email;
	private String openid;
	
	private Map<String,String> data = new HashMap<String,String> ();
	
	private File privatekey;
	
	public final String PAYMENT_GATEWAY_URL = "http://credits.browser-based-games.org/default/spend/";
	public final String TRACKER_GATEWAY_URL = "http://credits.browser-based-games.org/default/tracker/";
	
	/**
	 * Create a new Credits instance
	 * @param gametoken	Your personal game token
	 * @param site		The URL of your game
	 */
	public Credits (String gametoken, String site)
	{
		addData ("site", site);
		addData ("client", gametoken);
	}
	
	/*
	 * Setters
	 */
	
	/**
	 * If the user provided an email address, you should put it here.
	 * Mind that only validated email addresses should be used!
	 * Otherwise it's possible to act on behalf of another user.
	 * @param email		The (validated!) email address of the current user
	 */
	public void setEmail (String email)
	{
		this.email = email;
	}
	
	/**
	 * If the user is logged in using OpenID, you should set this attribute
	 * @param openid	The OpenID identity URL of the current user
	 */
	public void setOpenID (String openid)
	{
		this.openid = md5 (openid);
	}
	
	/**
	 * If you have your own referal system, you can set the ref key here (optional)
	 * @param referal	Referal key of the current user
	 */
	public void setReferal (String referal)
	{
		addData ("ref", referal);
	}
	
	/**
	 * Can be set if the user is currently playing the game in fullscreen modus
	 * (required by some networks and received from the OpenID AX attribute)
	 * @param fullscreen	TRUE if the user is playing fullscreen
	 */
	public void setFullscreen (boolean fullscreen)
	{
		addData ("fullscreen", fullscreen ? "1" : "0");
	}
	
	/**
	 * Sets the container of the current user (as received from the OpenID AX attribute)
	 * @param container	The container in which the game is currently running.
	 */
	public void setContainer (String container)
	{
		addData ("container", container);
	}
	
	/**
	 * Sets the language of the current user (optional)
	 * @param language	Language in 2-letter form
	 */
	public void setLanguage (String language)
	{
		addData ("lang", language);
	}
	
	/**
	 * Set the private key that will be used for signing the requests
	 * Key must be in PKCS8
	 * @param key	Private key
	 */
	public void setPrivateKeyFile (File key)
	{
		this.privatekey = key;
	}
	
	/**
	 * Allows you to set the userid of the current user (optional)
	 * Mostly used for debugging.
	 * @param userid	The user id of the current user
	 */
	public void setUserId (String userid)
	{
		addData ("userid", userid);
	}
	
	/**
	 * Allows you to set the birthday of the current user (optional)
	 * @param birthday	The birthday of the user
	 */
	public void setBirthday (Date birthday)
	{
		long bd = birthday.getTime () / 1000;
		addData ("birthday", String.valueOf(bd));
	}
	
	/**
	 * Allows you to set the gender of the current user (optional)
	 * @param gender	Can be "male" or "female"
	 */
	public void setGender (String gender)
	{
		addData ("gender", gender.equals ("male") ? "male" : "female");
	}
	
	/**
	 * Allows you to set custom data to view in the statistic pages (optional)
	 * @param key	A key for the data
	 * @param value	The data itself
	 */
	public void setCustomData (String key, String value)
	{
		addData ("custom_" + key, value);
	}
	
	/*
	 * Methods
	 */
	
	/**
	 * Get the amount of credits the user currently has
	 * 
	 * @return	the amount of credits the user currently has
	 */
	public int getCredits () throws RuntimeException
	{
		isValidData ();
		
		Map<String,String> tmp = new HashMap<String,String> ();
		
		if (email != null)
			tmp.put ("email", email);
	
		if (openid != null)
			tmp.put ("openid", openid);
		
		tmp.putAll (data);
		
		String url = getSignedUrl (PAYMENT_GATEWAY_URL + "getcredits/", tmp);;
		String content = file_get_contents (url);

		int amount = 0;
		
		try
		{
			amount = Integer.parseInt (content);
		}
		catch (Exception e)
		{
			
		}
		//System.out.println (content);
		return amount;
	}
	
	/**
	 * Generate the URL that should be used to request credits from the user.
	 * 
	 * @param credits	The amount of credits requested
	 * @param desc		Description for this transfer
	 * @param callback	The callback that will be called when the transfer is done
	 * @param action	String to "tag" transfers (for statistics)
	 * @return			The URL that should be used.
	 */
	public String requestCredits (int credits, String desc, String callback, String action) throws RuntimeException
	{
		isValidData ();
		
		Map<String,String> tmp = new HashMap<String,String> ();
		
		if (email != null)
			tmp.put ("email", email);
	
		if (openid != null)
			tmp.put ("openid", openid);
		
		tmp.put ("amount", String.valueOf(credits));
		tmp.put ("callback", callback);
		tmp.put ("description", desc);
		tmp.put ("action", action);
		
		tmp.putAll (data);
		
		return getSignedUrl (PAYMENT_GATEWAY_URL, tmp);
	}
	
	/**
	 * Refund a certain amount of credits.
	 * 
	 * @param credits	The amount of credits requested
	 * @param desc		Description for this transfer
	 * @param callback	The callback that will be called when the transfer is done
	 * @param action	String to "tag" transfers (for statistics)
	 * @return			The URL that should be used.
	 */
	public boolean refundCredits (int credits, String desc, String action) throws RuntimeException
	{
		isValidData ();
		
		Map<String,String> tmp = new HashMap<String,String> ();
		
		if (email != null)
			tmp.put ("email", email);
	
		if (openid != null)
			tmp.put ("openid", openid);
		
		tmp.put ("amount", String.valueOf(credits));
		tmp.put ("description", desc);
		tmp.put ("action", action);
		
		tmp.putAll (data);
		
		String url = getSignedUrl (PAYMENT_GATEWAY_URL + "refundcredits/", tmp);;
		String content = file_get_contents (url);

		int amount = 0;
		
		try
		{
			amount = Integer.parseInt (content);
		}
		catch (Exception e)
		{
			
		}
		//System.out.println (content);
		return amount == 1;
	}
	
	/**
	 * Generate the URL to use in the tracker iframe 
	 */
	public String getTrackerUrl (String tracker)
	{
		Map<String,String> tmp = new HashMap<String,String> ();
		tmp.put("tracker", tracker);
		return getSignedUrl (TRACKER_GATEWAY_URL, tmp);
	}
	
	/**
	 * Checks if a given transaction is valid
	 * 
	 * @param id		The id of the transaction (as provided by the callback API)
	 * @param secret	The secret of the transaction (as provided by the callback API)
	 */
	public boolean isRequestValid (int id, String secret) throws RuntimeException
	{
		Map<String,String> tmp = new HashMap<String,String> ();
		
		tmp.put ("id", String.valueOf(id));
		tmp.put ("secret", secret);
		
		String url = getSignedUrl (PAYMENT_GATEWAY_URL + "check/", tmp);;
		String content = file_get_contents (url);
		
		content = content.substring (0, 1);
		return content.equals ("1");
	}
	
	/*
	 * Private methodes
	 */
	
	/**
	 * Check to see if all required data is set
	 */
	private void isValidData () throws RuntimeException
	{
		if (email != null && !email.isEmpty())
			return;
		
		if (openid != null && !openid.isEmpty ())
			return;
		
		throw new RuntimeException ("At least one of the following is required: email, openid");
	}
	
	private String getSignedUrl (String url, Map<String,String> datas)
	{
		long timestamp = System.currentTimeMillis() / 1000;
		datas.put("time", String.valueOf (timestamp));
		
		String str = prepareDataString (datas);
		String signature = sign (str);
		
		try 
		{
			signature = URLEncoder.encode (signature, "UTF-8");
		} 
		catch (UnsupportedEncodingException e) 
		{
			e.printStackTrace();
		}
		
		return url + "?" + str + "&signature=" + signature;
	}
	
	private void addData (String key, String value)
	{
		data.put (key, value);
	}
	
	private String sign (String key)
	{
		PKCS8RSASigner signer = new PKCS8RSASigner (privatekey);
		
		byte[] signature = null;
		
		try 
		{
			signature = signer.getSignature(key.getBytes());
			
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		
		return Base64.encodeBytes (signature);
	}
	
	private String prepareDataString (Map<String,String> map)
	{
		Set<String> keys = map.keySet ();
		TreeSet<String> sorted = new TreeSet<String> ();
		sorted.addAll (keys);
		
		String output = "";
		Iterator<String> it = sorted.iterator();
		
		String currentkey = null;
		
		while (it.hasNext ())
		{
			currentkey = it.next ();
			
			try
			{
				output += currentkey + "=" + URLEncoder.encode (map.get (currentkey), "UTF-8") + "&";
			}
			catch (Exception e)
			{
				System.out.println (e);
			}
		}
		
		output = output.substring (0, output.length() - 1);
		
		return output;
	}
	
	private String md5 (String value)
	{
		StringBuffer hexString = new StringBuffer();
		
		MessageDigest digest;
		try 
		{
			digest = java.security.MessageDigest.getInstance("MD5");
		    digest.update(value.getBytes());
		    byte[] hash = digest.digest();
		    
			for (int i=0;i < hash.length;i++) 
			{
				hexString.append(Integer.toHexString(0xFF & hash[i]));
			}
		} 
		catch (NoSuchAlgorithmException e) 
		{
			e.printStackTrace();
		}
	    
		return hexString.toString ();
	}
	
	private String file_get_contents (String url)
	{
		String output = "";
		
		//System.out.println ("Fetching " + url);
		
		try 
		{
			BufferedReader reader = new BufferedReader (new InputStreamReader (new URL(url).openStream()));

			String line = reader.readLine ();
		
			while (line != null)
			{
				output += "\n" + line;
				line = reader.readLine ();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		output = output.substring(1);
		
		//System.out.println (output);
		
		return output;
	}
}
